/*
 *  Copyright 2007-2008 by Matthias Buch-Kromann <mbk.isv@cbs.dk>.
 *  *
 *  This file is part of the Open-source Dependency Toolkit (OSDT),
 *  see http://code.google.com/p/open-source-dependency-toolkit.
 *  
 *  The OSDT is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as 
 *  published by the Free Software Foundation, either version 3 of 
 *  the License, or (at your option) any later version.
 * 
 *  The OSDT is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 * 
 *  You should have received a copy of the GNU Lesser General Public 
 *  License along with the OSDT in the files COPYING and COPYING.LESSER.
 *  If not, see http://www.gnu.org/licenses.
 */

package org.osdtsystem.graphs.io;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import org.osdtsystem.exceptions.FeatureNotFoundException;
import org.osdtsystem.exceptions.LayerNotFoundException;
import org.osdtsystem.exceptions.LocationNotFoundException;
import org.osdtsystem.exceptions.NodeNotFoundException;
import org.osdtsystem.exceptions.NonUniqueNameException;
import org.osdtsystem.exceptions.OsdtSyntaxException;
import org.osdtsystem.graphs.Graph;
import org.osdtsystem.graphs.views.View;
import org.osdtsystem.values.Layer;
import org.osdtsystem.values.NodeID;
import org.osdtsystem.graphs.views.ViewFactory;
import org.osdtsystem.values.Location;

/**
 *
 * @author Matthias Buch-Kromann <mbk.isv@cbs.dk>
 */
public class OsdtReader implements GraphReader {
    private Reader reader;
    private Graph graph;
    private GraphIDs ids;
    Scanner scanner;
    List<EdgeDeclaration> edgeDeclarations;
    List<String> viewDeclarations;
    Layer currentLayer = null;
    ObjectDictionary dictionary;
    
    public OsdtReader(Reader reader, Graph graph, ObjectDictionary dictionary) {
        this.reader = reader;
        this.graph = graph;
        this.ids = new GraphIDs(graph);
        this.scanner = new Scanner(reader);
        this.dictionary = dictionary;
    }
    
    public OsdtReader(String string, Graph graph, ObjectDictionary dictionary) {
        this(new StringReader(string), graph, dictionary);
    }

    public OsdtReader(String string, Graph graph) {
        this(string, graph, new ObjectDictionary());
    }
    
    @Override
    public String encoding() {
        return "osdt";
    }

    @Override
    public Graph read(Reader reader) throws OsdtSyntaxException, IOException {
        close();
        this.reader = reader;
        this.scanner = new Scanner(reader);
        return next();
    }

    @Override
    public Graph read(String string) throws OsdtSyntaxException, IOException {
        return read(new StringReader(string));
    }

    @Override
    public void close() throws IOException {
        reader.close();
    }

    @Override
    public Graph next() throws OsdtSyntaxException {
        // Create graph and maps/lists for layers, nodes, and edges
        edgeDeclarations = new ArrayList<EdgeDeclaration>();
        viewDeclarations = new ArrayList<String>();
        
        // Scan lines
        scanLines();
        
        // Process edges
        for (EdgeDeclaration e : edgeDeclarations)
            e.addEdge(graph, ids, dictionary);
        
        // Process views
        for (String viewDeclaration : viewDeclarations) {
            Matcher m = OsdtHelper.osdtViewPattern.matcher(viewDeclaration);
            if (m.matches()) {
                Integer viewID = Integer.parseInt(m.group(1));
                String name = m.group(2);
                String encoding = m.group(3);
                String engine = m.group(4);
                View view = ViewFactory.decodeView(engine, encoding, graph, ids);

                System.out.print("ADDING VIEW (" + encoding + "): ");
                System.out.print(view);
                System.out.print("\n");
                
                // Add view to graph if non-null
                if (view != null) {
                    try {
                        graph.addView(view, name);
                        ids.add(view, viewID);
                        view.recompute();
                    } catch (NonUniqueNameException ex) {
                        Logger.getLogger(OsdtReader.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
        }
        
        // Return
        return graph;
    }
    
    void scanLines() throws OsdtSyntaxException {
        // Scan input line by line
        while (scanner.hasNext()) {
            // Read next line
            String line = scanner.nextLine();

            // Match next line with OSDT edge/node/layer/comment patterns
            Matcher m;
            if ((m = OsdtHelper.osdtEdgePattern.matcher(line)).matches()) {
                readEdgeLine(currentLayer, m.group(1), m.group(2), m.group(3));
            } else if ((m = OsdtHelper.osdtNodePattern.matcher(line)).matches()) {
                readNodeLine(currentLayer, m.group(1), m.group(2));                
            } else if ((m = OsdtHelper.osdtLayerPattern.matcher(line)).matches()) {
                readLayerLine(Integer.valueOf(m.group(1)), m.group(3), m.group(6));
            } else if ((m = OsdtHelper.osdtViewPattern.matcher(line)).matches()) {
                viewDeclarations.add(line);
            } else if (! OsdtHelper.osdtCommentPattern.matcher(line).matches()) {
                // throw new OsdtSyntaxException("Unrecognized line in OSDT encoding: " + line);
                System.out.println("Unrecognized line in OSDT encoding: " + line);
            }
        }            
    }

    void readLayerLine(Integer layerID, String layerName, String featvals) throws OsdtSyntaxException {
        // Layer pattern: 2=layer id, 3=layer name, 4=super layer string, 6=feat-val string
        if (ids.getLayer(layerID) != null) {
            // Layer is already defined: throw exception if groups 3-6 are defined, 
            // or simply change current layer
            currentLayer = ids.getLayer(layerID);
            if (layerName != null || featvals != null)
                throw new OsdtSyntaxException(
                        "Layer defined more than once in OSDT encoding at layer " + layerID);
        }  else if (layerName != null) {
            // Create layer
            currentLayer = graph.addLayer(layerName);
            ids.add(currentLayer, layerID);

            // Process feature string
            Matcher fMatcher = OsdtHelper.osdtFeaturePattern.matcher(featvals);
            while (fMatcher.find()) {
                try {
                    graph.addLayerFeature(currentLayer, dictionary.compileFeature(fMatcher.group(1)));
                } catch (LayerNotFoundException ex) {
                    throw new OsdtSyntaxException("Unexpected layer error in OSDT encoding for layer " 
                            + layerID, ex);
                }
            }
        }
    }
    
    void readNodeLine(Layer layer, String nodeID, String featvals) throws OsdtSyntaxException {
        try {
            // Compute node ID and throw exception if node already exists
            Integer id = Integer.decode(nodeID);
            if (ids.getNode(id) != null)
                throw new OsdtSyntaxException("Node " + id 
                        + " defined more than once in OSDT encoding");
            else {
                // Create node
                NodeID node = graph.addNode(layer);
                ids.add(node, id);
                readFeatureValues(node, featvals);
            }
        } catch (NodeNotFoundException ex) {
            throw new OsdtSyntaxException("Invalid node when reading OSDT encoding at node "
                    + nodeID, ex);
        } catch (LayerNotFoundException ex) {
            throw new OsdtSyntaxException("Invalid layer when reading OSDT encoding at node " 
                    + nodeID, ex);
        } catch (LocationNotFoundException ex) {
            throw new OsdtSyntaxException("Invalid location when reading OSDT encoding at node "
                    + nodeID, ex);
        } catch (FeatureNotFoundException ex) {
            throw new OsdtSyntaxException("Invalid feature when reading OSDT encoding at node "
                    + nodeID, ex);
        }
    }
    
    void readEdgeLine(Layer layer, String sin, String sout, String featvals) throws OsdtSyntaxException {
        // readEdgeLine(currentLayer, matcher.group(1), matcher.group(2), matcher.group(3));
        // Edge pattern: 1=in-node id, 2=out-node id, 3=feat-val string
        EdgeDeclaration e = new EdgeDeclaration(layer, sin, sout, featvals);

        // Create edge right away, if possible, or cache it for later creation
        if (ids.id(e.in) != null && ids.id(e.out) != null) 
            e.addEdge(graph, ids, dictionary);
        else 
            edgeDeclarations.add(e);
    }

    void readFeatureValues(Location location, String featvals) throws LocationNotFoundException, FeatureNotFoundException {
        // Process feature-value string
        Matcher fvMatcher = OsdtHelper.osdtFeatureValuePattern.matcher(featvals);
        while (fvMatcher.find())
            graph.setFeatureValue(location, 
                    dictionary.compileFeature(OsdtHelper.unquote(fvMatcher.group(1))),
                    dictionary.compile(OsdtHelper.unquote(fvMatcher.group(2))));
    }
}
